/*
 * Copyright (c) 2024 Black Sesame Technologies
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "dal_rticonnextdds/utils/ParticipantMonitor.hpp"
#include "dal_rticonnextdds/utils/ParticipantGuidMap.hpp"

using namespace std::chrono_literals;

static bool retreval_handle_seq(int index,
                                const DDS_InstanceHandleSeq& ins_seq,
                                rti_participant_guid_num_s& guid_num,
                                DDS_InstanceHandle_t& ins_handle)
{
    if (index < 0 || index >= ins_seq._length)
        return false;
    // ins_handle = ins_seq.get_at(index);
    ins_handle = *(DDS_InstanceHandleSeq_get_reference(&ins_seq, index));
    guid_num.copy_from_rti(ins_handle);
    return true;
}

uint32_t rti_monitor_timestamp()
{
    int64_t nanosec = std::chrono::system_clock::now().time_since_epoch().count();
    int64_t millsec = nanosec / 1000000;
    return millsec & 0xFFFFFFFF;
}

void rti_participant_monitor(DalDomainParticipantImpl* d_participant_impl)
{
    if (!(d_participant_impl && d_participant_impl->RtiddsDomainParticipant()))
        return;
    uint32_t monitor_born_timestamp = rti_monitor_timestamp();

    DDS_DomainParticipant* local_participant = d_participant_impl->RtiddsDomainParticipant();
    dal_participant_monitor_callback monitor_callback = d_participant_impl->get_monitor_callback();
    void* monitor_callback_data = d_participant_impl->get_monitor_callback_data();

    std::set<rti_participant_guid_num_s> discoveried_part_list;
    std::set<rti_participant_guid_num_s> latest_discoveried_part_list;
    std::set<rti_participant_guid_num_s>::iterator it;
    rti_participant_discovery_info_s* discovery_info = nullptr;
    DDS_ReturnCode_t ret = DDS_RETCODE_ERROR;

    while (d_participant_impl->get_participant_monitor_flag().load()) {
        DDS_InstanceHandleSeq ins_seq;
        DDS_InstanceHandle_t ins_handle;
        rti_participant_guid_num_s guid_num;

        ret = DDS_DomainParticipant_get_discovered_participants(local_participant, &ins_seq);
        if (ret != DDS_RETCODE_OK) {
            continue;
        }

        for (int i = 0; i < ins_seq._length; i++) {
            DDS_ParticipantBuiltinTopicData participant_data;

            if (!retreval_handle_seq(i, ins_seq, guid_num, ins_handle)) {
                continue;
            }

            DDS_DomainParticipant_get_discovered_participant_data(
                local_participant, &participant_data, &ins_handle);
            discovery_info = (rti_participant_discovery_info_s*)(DDS_OctetSeq_get_contiguous_buffer(
                &participant_data.user_data.value));
            latest_discoveried_part_list.insert(guid_num);

            if (discoveried_part_list.count(guid_num) != 0) {
                continue;
            }
            discoveried_part_list.insert(guid_num);
            if (discovery_info->born_time <= monitor_born_timestamp) {
                continue;
            }
            dal_participant_monitor_info_s* d_info = new dal_participant_monitor_info_s;

            memcpy(d_info->guid.guidprefix.value, ins_handle.keyHash.value, 12);
            memcpy(d_info->guid.entity_id.value, ins_handle.keyHash.value + 12, 4);

            d_info->participant_name.length = 0;
            d_info->participant_name.str = nullptr;

            if (participant_data.participant_name.name) {
                uint32_t name_len = strlen(participant_data.participant_name.name);
                if (name_len > 0) {
                    d_info->participant_name.length = name_len;
                    d_info->participant_name.str = (char*)malloc(name_len + 1);
                    memcpy(d_info->participant_name.str,
                           participant_data.participant_name.name,
                           name_len);
                    (d_info->participant_name.str)[name_len] = '\0';
                }

                std::string name_str = std::string(participant_data.participant_name.name);
                ParticipantGuidMapInsert(DalGuidForMap(d_info->guid), name_str);
            }

            d_info->status = DAL_PARTICIPANT_ONLINE;

            monitor_callback(d_info, monitor_callback_data);
            if (d_info->participant_name.str) {
                delete d_info->participant_name.str;
                d_info->participant_name.str = nullptr;
            }

            delete d_info;
        }

        for (it = discoveried_part_list.begin(); it != discoveried_part_list.end();) {
            DDS_ParticipantBuiltinTopicData participant_data;

            if (latest_discoveried_part_list.count(*it) == 0) {
                dal_participant_monitor_info_s* d_info = new dal_participant_monitor_info_s;

                memcpy(ins_handle.keyHash.value, &(it->first_half), 8);
                memcpy(ins_handle.keyHash.value + 8, &(it->second_half), 8);

                memcpy(d_info->guid.guidprefix.value, ins_handle.keyHash.value, 12);
                memcpy(d_info->guid.entity_id.value, ins_handle.keyHash.value + 12, 4);

                d_info->participant_name.length = 0;
                d_info->participant_name.str = nullptr;

                std::string name;
                bool ret = ParticipantGuidMapFind(DalGuidForMap(d_info->guid), name);
                if (ret && name.length() > 0) {
                    d_info->participant_name.str = const_cast<char*>(name.c_str());
                    d_info->participant_name.length = name.length();
                }

                d_info->status = DAL_PARTICIPANT_OFFLINE_ABNORMALLY;

                monitor_callback(d_info, monitor_callback_data);
                it = discoveried_part_list.erase(it);
                delete d_info;
            } else {
                ++it;
            }
        }
        latest_discoveried_part_list.clear();
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }
}
